package ezxpns.util;

import java.util.*;

public class UndoWrapper<TObj extends Wrappable<TView,CommandGroup>,TView> implements Undoable<TView>
{
	private Wrappable<TView,CommandGroup> object;
	private Key<CommandGroup> key;
	private class Node
	{
		Node parent=null;	// parent node ; null for the root
		Node child=null;	// first child ; null for a leaf
		Node prev=this;		// previous sibling ; wraps from the first child to the last child
		CommandGroup cmdGroup=null;		// group of commands to get to this node ; null for the root
		CommandGroup undoGroup=null;	// group of commands to get to the parent node ; null for the root
		Node() { }
	}
	private Node root;
	private Node current;
	public UndoWrapper(TObj object2) throws Exception
	{
		object=object2;
		key=object.acquire();
		if( key==null ) throw new Exception("Cannot acquire object!");
		root=new Node();
		root.prev=root;
		current=root;
	}
	public void exec(CommandGroup cmdGroup)
	{
		ArrayList<CommandGroup> undoGroups=new ArrayList<CommandGroup>();
		for(int a=0;a<cmdGroup.size();a++)
		{
			Command<?> command=cmdGroup.get(a);
			CommandGroup undoGroup=key.exec(command);
			if( undoGroup==null )
			{	// not undoable command
				root=current;
				current.parent=null;
				undoGroups=new ArrayList<CommandGroup>();
				continue;
			}
			if( undoGroup.size()>0 ) undoGroups.add(undoGroup);
		}
		if( undoGroups.size()==0 )
		{	// nothing to undo
			return;
		}
		Node node=new Node();
		node.parent=current;
		if( current.child==null )
		{
			node.prev=node;
			current.child=node;
		}
		else
		{
			node.prev=current.child.prev;
			current.child.prev=node;
		}
		node.cmdGroup=cmdGroup.copy();
		node.undoGroup=new CommandGroup();
		for(int a=undoGroups.size()-1;a>=0;a--) node.undoGroup.add(undoGroups.get(a));
		current=node;
	}
	public void undo()
	{
		Node parent=current.parent;
		if( parent==null ) return;
		CommandGroup group=current.undoGroup;
		for(int a=0;a<group.size();a++) key.exec(group.get(a));
		current=parent;
	}
	public void redo()
	{
		Node child=current.child;
		if( child==null ) return;
		child=child.prev;
		CommandGroup group=child.cmdGroup;
		for(int a=0;a<group.size();a++) key.exec(group.get(a));
		current=child;
	}
	public void hop()
	{
		Node prev=current.prev;
		if( prev==current ) return;
		CommandGroup group;
		group=current.undoGroup;
		for(int a=0;a<group.size();a++) key.exec(group.get(a));
		group=prev.cmdGroup;
		for(int a=0;a<group.size();a++) key.exec(group.get(a));
		current=prev;
	}
	public boolean canUndo()
	{
		return current.parent!=null;
	}
	public boolean canRedo()
	{
		return current.child!=null;
	}
	public boolean canHop()
	{
		return current.prev!=current;
	}
	public TView getView()
	{
		return object.getView();
	}
}
